#pragma once
// System Include
#include <stdio.h>
#include <string>
#include <iostream>
#include <fstream>
#include <direct.h>
#include <random>
// Local Include
// ThirdParty Include
#include <cuda_runtime.h>
#include <device_launch_parameters.h>

#ifdef __cpp_lib_source_location
#include <source_location>
// https://en.cppreference.com/w/cpp/utility/source_location
inline void HANDLE_ERROR(const int& Status, const std::source_location location = std::source_location::current()) {
	if (Status != 0)
		std::cerr << "Error Cuda Status(" << Status << ") : [" << location.function_name() << "@" << location.file_name() << "," << location.line() << "]" << std::endl;
}
#else
#define HANDLE_ERROR(Status) \
	if (Status != 0) \
		std::cerr << "Error Cuda Status("<< Status << ") : [" << __func__ << "@" << __FILE__ << "," << __LINE__ << "]" << std::endl;
#endif

namespace Utils{
	
	#define SHOW_DEBUG_INFO

	inline unsigned int Index3D(unsigned int X, unsigned int Y, unsigned int Z, unsigned int Size) {
		return (Z * Size + Y) * Size + X;
	}

	inline void CudaHostAllocSafe(cudaError_t& CudaStatus, void** Array, const size_t& ArraySize) {
		CudaStatus = cudaHostAlloc(Array, ArraySize, cudaHostAllocDefault);
		#ifdef SHOW_DEBUG_INFO
		if (CudaStatus != cudaSuccess) {
			std::cerr << "cudaHostAlloc failed! CudaStatus: " << CudaStatus << std::endl;
		} else {
			std::cout << "cudaHostAlloc succeed!"<< std::endl;
		}
		#endif
	}

	inline void CudaMallocSafe(cudaError_t& CudaStatus, void** Array, const size_t& ArraySize) {
		CudaStatus = cudaMalloc(Array, ArraySize);
		#ifdef SHOW_DEBUG_INFO
		if (CudaStatus != cudaSuccess) {
			std::cerr << "cudaMalloc failed! CudaStatus: " << CudaStatus << std::endl;
		}
		else {
			std::cout << "cudaMalloc succeed!" << std::endl;
		}
		#endif
	}

	inline void CudaMemcpySafe(cudaError_t& CudaStatus, void* ArrayDst, const void* ArraySrc, const size_t& ArraySize, cudaMemcpyKind Kind = cudaMemcpyHostToDevice) {
		CudaStatus = cudaMemcpy(ArrayDst, ArraySrc, ArraySize, Kind);
		#ifdef SHOW_DEBUG_INFO		
		if (CudaStatus != cudaSuccess) {
			std::cerr << "cudaMemcpy failed! CudaStatus: " << CudaStatus << std::endl;
		}
		else {
			std::cout << "cudaMemcpy succeed!" << std::endl;
		}
		#endif
	}

	inline void CudaMemcpyToSymbolSafe(cudaError_t& CudaStatus, const void* ArrayDst, const void* ArraySrc, const size_t& ArraySize, const size_t& Offset = 0u, cudaMemcpyKind Kind = cudaMemcpyHostToDevice) {
		CudaStatus = cudaMemcpyToSymbol(ArrayDst, ArraySrc, ArraySize);//, Offset, Kind);
		#ifdef SHOW_DEBUG_INFO
		if (CudaStatus != cudaSuccess) {
			std::cerr << "cudaMemcpyToSymbol failed! CudaStatus: " << CudaStatus << std::endl;
		} else {
			std::cout << "cudaMemcpyToSymbol succeed!" << std::endl;
		}
		#endif
	}

	inline void CudaMemsetSafe(cudaError_t& CudaStatus, void* ArrayDst, const int& Value, const unsigned int& ArraySize) {
		CudaStatus = cudaMemset(ArrayDst, Value, ArraySize * sizeof(float));
		#ifdef SHOW_DEBUG_INFO
		if (CudaStatus != cudaSuccess) {
			std::cerr << "cudaMemset failed! CudaStatus: " << CudaStatus << std::endl;
		}
		else {
			std::cout << "cudaMemset succeed!" << std::endl;
		}
		#endif
	}

	inline void CudaOccupancyMaxActiveBlocksPerMultiprocessorSafe(cudaError_t& CudaStatus, int* OutNumBlocks, void* func, int blockSize, size_t dynamicSMemSize) {
		CudaStatus = cudaOccupancyMaxActiveBlocksPerMultiprocessor(OutNumBlocks, func, blockSize, dynamicSMemSize);
		#ifdef SHOW_DEBUG_INFO
		if (CudaStatus != cudaSuccess) {
			std::cerr << "cudaOccupancyMaxActiveBlocksPerMultiprocessor failed! CudaStatus: " << CudaStatus << std::endl;
		}
		else {
			std::cout << "cudaOccupancyMaxActiveBlocksPerMultiprocessor succeed!" << std::endl;
		}
		#endif
	}

	/// <summary>
	/// Generate Random [R]eal Numbers to Array 
	/// </summary>
	/// <typeparam name="T"></typeparam>
	/// <param name="Array"></param>
	/// <param name="ArraySize"></param>
	template<typename T>
	void GenRandomArrayR(T* Array, const unsigned int& ArraySize);
	template<typename T>
	void GenOneArrayR(T* Array, const unsigned int& ArraySize);
	template<typename T>
	void GenIndexArrayR(T* Array, const unsigned int& ArraySize);

	/// <summary>
	/// Generate Random [I]nteger Numbers to Array 
	/// </summary>
	/// <typeparam name="T"></typeparam>
	/// <param name="Array"></param>
	/// <param name="ArraySize"></param>
	template<typename T>
	void RandomArrayI(T* Array, const unsigned int& ArraySize);

	void Write2DArrayToFile(const float* ToWriteMatrix, const int& W, const int& H, std::string FileName);
	void WriteCuda2DArrayToFile(const float* ToWriteMatrix, float* ToReceiveMatrix, const int& W, const int& H, std::string FileName);
}

template<typename T>
void Utils::GenRandomArrayR(T* Array, const unsigned int& ArraySize) {
	std::random_device RandomDevice;
	std::mt19937 Generator(RandomDevice());
	std::uniform_real_distribution<T> Distru(4.f, 16.f);

	for (unsigned int i = 0; i < ArraySize; i++) Array[i] = Distru(Generator);
}

template<typename T>
void Utils::GenOneArrayR(T* Array, const unsigned int& ArraySize) {
	for (unsigned int i = 0; i < ArraySize; i++) Array[i] = 1.0;
}

template<typename T>
void Utils::GenIndexArrayR(T* Array, const unsigned int& ArraySize) {
	for (unsigned int i = 0; i < ArraySize; i++) Array[i] = i * 1.0;
}
